package pl.cezary.butler.walili.server;

import java.rmi.registry.Registry;
import pl.cezary.butler.walili.common.IServerFacadeRemote;
import pl.cezary.butler.walili.common.TaskStatus;
import pl.cezary.butler.walili.common.Task;
import pl.cezary.butler.walili.common.Query;

import java.io.Serializable;
import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ServerRMIUserFacade implements Serializable, IServerFacadeRemote {

    private static final int WARN_USER_TASKS = 14;
    private static final int MAX_USER_TASKS = 32;
    static final long serialVersionUID = 5389484610880099522L;
    private static final Logger log = Logger.getLogger(ServerRMIUserFacade.class.getName());
    private DbBasedServerFacade fasada = DbBasedServerFacade.getInstance();
    //TODO tutaj bedziemy trzymac zbior zadan ktore ma klient i tutaj bedziemy go obrabiac
    final Set<ServerTask> zadaniaUzytkownika = new ConcurrentSkipListSet<ServerTask>();
    final AtomicInteger userTaskCount = new AtomicInteger();
    private UserId user;

    public ServerRMIUserFacade(UserId uid) {
        setUser(uid);
    }

    public UserId getUser() {
        return user;
    }

    @Override
    public List<Task> processQuery(Query zapytanie) {
        return fasada.processQuery(zapytanie);
    }

    @Override
    public void sendTask(Task zadanie) {
        fasada.sendTask(zadanie);
    }

    //TODO poprawic implementacje
    @Override
    public Task getTask() {
        final int l_userTaskCount = zadaniaUzytkownika.size();
        if (l_userTaskCount < MAX_USER_TASKS) {
            ServerTask zad = ServerTask.getZadanieSerwer(fasada.getTask());
            if (zad != null && !zad.getOstatniPrzetwarzajacy().equals(user)) {
                return (Task) zarejestrujPobZadanie(zad);
            } else {
                if (zad != null) {
                    Task zad2 = getTask();
                    fasada.sendTask(zad);
                    return zad2;
                }
            }
        }else{
            log.log(Level.INFO,"User has to many ({0}) tasks, not even trying to supply more.",l_userTaskCount);
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException ex) {
                log.fine("Interrupted while trying to delay answer to the client");
            }
        }
        return null;
    }

    @Override
    public List<Task> getTasks(int n) {
        final int l_userTaskCount = zadaniaUzytkownika.size();
        final int couter = this.userTaskCount.get();
        if(l_userTaskCount!=couter){
            log.log(Level.WARNING,"User task count seems to be wrong collection: {0}, counter: {1}",new Object[]{l_userTaskCount, couter});
        }
        if(l_userTaskCount+n>MAX_USER_TASKS){            
            n=MAX_USER_TASKS-l_userTaskCount;
            log.log(Level.INFO, "User would have to many tasks, trying to supply less ({0})",n);
            if(n<=0){
                log.log(Level.FINE,"It's impossible to supply les than zero tasks, returinng nothing");
                return Collections.EMPTY_LIST;                
            }
        }        
        return Task.trim((List<Task>) zarejestrujPobZadanie(fasada.getTasks(n)));
    }

    @Override
    public void logout() {
        
        if(fasada==null){//niezalogowany nie ma nic do zrobienia
            log.fine("Logging out not logged in facade (probably already logged out.");
            if(!zadaniaUzytkownika.isEmpty())
                log.log(Level.WARNING, "User is not logged in therfore, there should not be any tasks associated with him");
            return ;
        }
        synchronized (zadaniaUzytkownika) {
            log.log(Level.INFO, "Logging out. Marking {0} user tasks as interrupted", zadaniaUzytkownika.size());
            for (ServerTask task : zadaniaUzytkownika) {
                task.setStatus(TaskStatus.PRZERWANE);                
            }
            zarejestruWykZadanie(new ArrayList(zadaniaUzytkownika));
        }
        try {
            TimeUnit.MILLISECONDS.sleep(100);
            fasada.prepareForClose();
            final Registry registry = Server.getRegistry();
            if (registry != null) {
                registry.unbind(user.toString());
            }

        } catch (InterruptedException ex) {
            log.log(Level.SEVERE, null, ex);
        } catch (AccessException ex) {
            log.log(Level.SEVERE, "Improper method call", ex);
        } catch (RemoteException ex) {
            log.severe("RMI Communication exception");
        } catch (NotBoundException ex) {
            log.severe("Trying to log out user who is not logged in");
        } finally {
            fasada.close();
            fasada = null;
        }
    }

    @Override
    public void taskDone(Task zad) {
        log.log(Level.FINE, "Task done {0}/{1}", new Object[]{zad.getPolozenie(), zad.getCel()});
        ServerTask zadanie = new ServerTask(zad);
        zarejestruWykZadanie(ServerTask.getZadanieSerwer(zadanie));
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,regenBody=yes,id=DCE.DCCB3FE0-EFF8-3354-36F4-C5306CCA991D]
    // </editor-fold> 
    protected final void setUser(UserId val) {
        this.user = val;
    }

    /*
     * private class WatekRejestrujacyPobranieZadania extends Thread { //TODO
     * sprobowac polaczyc dwie klasy w jedno aby watek pracowal w tle caly czas
     * i zajmowal sie rejestracja pobran wykonan zadan; //pamietac o zatrzymaniu
     * watku przed wynullowaniem fasady !!! private final ServerTask zadanie;
     * private final List<? extends Task> zadaniaTh;
     *
     * public WatekRejestrujacyPobranieZadania(ServerTask z) { if (z == null) {
     * throw new RuntimeException(); }//TODO dodac komunikat; zadanie = z;
     * zadaniaTh = null; start(); }
     *
     * public WatekRejestrujacyPobranieZadania(List<? extends Task> z) { if (z
     * == null) { throw new RuntimeException(); }//TODO dodac komunikat; zadanie
     * = null; zadaniaTh = z; start(); }
     *
     *
     *
     * @Override public void run() { try { rejestracje.acquire();
     * zarejestruWykZadanie(zadanie); if (zadaniaTh != null) { for (Task zad :
     * zadaniaTh) { zarejestruWykZadanie((ServerTask) zad); } }
     *
     * } catch (InterruptedException ex) {
     * Logger.getLogger(ServerRMIUserFacade.class.getName()).log(Level.SEVERE,
     * null, ex); //wymyslic co z tym zrobic } finally { rejestracje.release();
     * } }
     *
     * public Object zarejestruj() { return (zadanie != null) ?
     * Task.trim(zadanie) : zadaniaTh; } }
     */
    private void zarejestruWykZadanie(ServerTask zad) {
        if (zad == null) {
            return;
        }
        userTaskCount.decrementAndGet();
        if (!zadaniaUzytkownika.remove(zad)) {
            log.log(Level.WARNING, "Task {0} was not found in user's task and could not be marked as removed.", zad);
        }
        fasada.registerTaskDone(user, zad);
    }
    
    private void zarejestruWykZadanie(Collection<ServerTask> zad) {
        if (zad == null) {
            return;
        }        
        userTaskCount.addAndGet(-1* zad.size());
        fasada.zarejestrujWykonanieZadan(user, zad); // TODO Może wersja z SeverTask byłaby lepsza
        if (!zadaniaUzytkownika.removeAll(zad)) {
            log.log(Level.WARNING, "Some task's were not fount in users tasks and could not be marked as removed.");
        }        
    }
    

    private List<? extends Task> zarejestrujPobZadanie(List<? extends Task> zad) {
        for (Task z : zad) {
            zarejestrujPobZadanie(z);
        }
        return zad;
    }

    private Task zarejestrujPobZadanie(Task zad) {
        if (zad == null) {
            log.fine("rejestruje pobranie zadania == null");
            return null;
        }        
        log.fine("rejestruje pobranie zadania");        
        assert (zad.isInitialized());
        userTaskCount.incrementAndGet();
        zadaniaUzytkownika.add((ServerTask) zad);
        if (zadaniaUzytkownika.size() > WARN_USER_TASKS) {
            log.log(Level.WARNING, "User has way to many ({0}) tasks not marked as finished", zadaniaUzytkownika.size());
        }
        fasada.registerTaskSent(user, zad);
        return zad;
    }
    //TODO ten wątek trzeba wywalić
    /*
     * private class WatekRejestrujacyWykonanieZadania extends Thread {
     *
     * private final ServerTask zadanie; private final List<? extends Task>
     * zadania;
     *
     * public WatekRejestrujacyWykonanieZadania(ServerTask z) {//TODO rozwazyc
     * ewentualna konwersje do zadanie serwer w watku !!!; if (z == null) {
     * throw new RuntimeException(); }//TODO dodac komunikat; zadanie = z;
     * zadania = null; start(); }
     *
     * public WatekRejestrujacyWykonanieZadania(List<? extends Task> z) { if (z
     * == null) { throw new RuntimeException(); }//TODO dodac komunikat; zadanie
     * = null; zadania = z; start(); }
     *
     *
     * @Override public void run() { try { rejestracje.acquire();
     * zarejestrujZadanie(zadanie); if (zadania != null) { for (Task zad :
     * zadania) { zarejestrujZadanie((ServerTask) zad); } } } catch
     * (InterruptedException ex) {
     * Logger.getLogger(ServerRMIUserFacade.class.getName()).log(Level.SEVERE,
     * null, ex); //TODO wymyslic co z tym zrobic } finally {
     * rejestracje.release(); }
     *
     * }
     *
     * public Object zarejestruj() { return (zadanie != null) ? zadanie :
     * zadania; } }
     */

    @Override
    public void retryFailedTasks() throws RemoteException {
        fasada.retryFailedTasks();
    }

    @Override
    public void retry(Task zad) {
        fasada.retry(zad);
    }

    @Override
    public void fillMimeTypes() throws RemoteException {
        fasada.fillMimeTypes();
    }
}
